/* SPDX-License-Identifier: GPL-2.0 */
/*
 * Copyright (C) 2020-2022 Huawei Technologies
 *
 * Author: huangzhenqiang <huangzhenqiang2@huawei.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, version 2 of the
 * License.
 *
 * File: hkip.h
 *		the header file of HKIP, providing kapi of HKIP.
 *
 */

#ifndef _HKIP_H
#define _HKIP_H

#ifdef CONFIG_EULEROS_HKIP_SERVER
#include <asm/memory.h>
#include <asm/pgtable-hwdef.h>
#include <linux/arm-smccc.h>
#endif

#ifndef __ASSEMBLY__
#define HKIP_SCAN_PGD		0xbf00ff03 /* Linux PGD scan */

#define HKIP_PGD_WRITE		0xbf00ff05
#define HKIP_PMD_WRITE		0xbf00ff06
#define HKIP_PUD_WRITE		0xbf00ff07
#define HKIP_PTE_WRITE		0xbf00ff08
#define HKIP_INSN_WRITE		0xbf00ff09 /* Linux instruction patch */
#define HKIP_MODULE_STATUS	0xbf00ff0a
#define HKIP_PTE_XCHG		0xbf00ff0b
#define HKIP_PGD_ALLOC		0xbf00ff0c
#define HKIP_HASH_READ		0xbf00ff0d

#define HKIP_ALLOW_MODULE_LOAD	1
#define HKIP_END_MODULE_LOAD	(-1)

#ifdef CONFIG_EULEROS_HKIP_SERVER
#define MODULE_VMALLOC_ADDR(x) (x < module_alloc_base + MODULES_VSIZE && x >= module_alloc_base)

#define HKIP_RO_POOL_SIZE	SZ_2M
#define HKIP_RO_POOL_ALIGN	SZ_2M

#ifdef CONFIG_EULEROS_HKIP_NON_FATAL
#define HKIP_WRITE_PTE(ptep, pte) WARN_ON(invoke_smc_fn(HKIP_PTE_WRITE, \
					(uintptr_t)ptep, \
					(uint64_t)pte_val(pte), 0))
#define HKIP_WRITE_PMD(pmdp, pmd) WARN_ON(invoke_smc_fn(HKIP_PMD_WRITE,	\
					(uintptr_t)pmdp, \
					(uint64_t)pmd_val(pmd), 0))
#define HKIP_WRITE_PUD(pudp, pud) WARN_ON(invoke_smc_fn(HKIP_PUD_WRITE,	\
					(uintptr_t)pudp, \
					(uint64_t)pud_val(pud), 0))
#define HKIP_WRITE_P4D(p4dp, p4d) WARN_ON(invoke_smc_fn(HKIP_PGD_WRITE,	\
					(uintptr_t)p4dp, \
					(uint64_t)p4d_val(p4d), 0))
#define HKIP_WRITE_PGD(pgdp, pgd) WARN_ON(invoke_smc_fn(HKIP_PGD_WRITE,	\
					(uintptr_t)pgdp, \
					(uint64_t)pgd_val(pgd), 0))
#else
#define HKIP_WRITE_PTE(ptep, pte) BUG_ON(invoke_smc_fn(HKIP_PTE_WRITE,	\
					(uintptr_t)ptep, \
					(uint64_t)pte_val(pte), 0))
#define HKIP_WRITE_PMD(pmdp, pmd) BUG_ON(invoke_smc_fn(HKIP_PMD_WRITE,	\
					(uintptr_t)pmdp, \
					(uint64_t)pmd_val(pmd), 0))
#define HKIP_WRITE_PUD(pudp, pud) BUG_ON(invoke_smc_fn(HKIP_PUD_WRITE,	\
					(uintptr_t)pudp, \
					(uint64_t)pud_val(pud), 0))
#define HKIP_WRITE_P4D(p4dp, p4d) BUG_ON(invoke_smc_fn(HKIP_PGD_WRITE,	\
					(uintptr_t)p4dp, \
					(uint64_t)p4d_val(p4d), 0))
#define HKIP_WRITE_PGD(pgdp, pgd) BUG_ON(invoke_smc_fn(HKIP_PGD_WRITE,	\
					(uintptr_t)pgdp, \
					(uint64_t)pgd_val(pgd), 0))
#endif /* CONFIG_EULEROS_HKIP_NON_FATAL */

/* exchange is always fatal for now */
#define HKIP_EXCHAGE_PTE(ptep, pte) \
	({								\
		struct arm_smccc_res res;				\
		arm_smccc_smc(HKIP_PTE_XCHG, (uintptr_t)ptep,		\
			(uint64_t)pte, 0, 0, 0, 0, 0, &res);		\
		BUG_ON(res.a0);						\
		res.a1;							\
	})

enum hkip_safety_level_type {
	HKIP_CLOSE_LEVEL = 0,
	HKIP_NORMAL_LEVEL = 1,
	HKIP_STRICT_LEVEL = 2,
};

extern atomic64_t ro_mem_block;
extern phys_addr_t ro_mem_block_start, ro_mem_block_end;
extern bool hkip_status;
extern atomic64_t hkip_dumpstack;
extern enum hkip_safety_level_type hkip_safety_level;
extern bool hkip_early_alloc;

/* Is the ptr part of the R/O pool reserved at boot */
bool hkip_ro_pool(void *ptr);

/* Allocate/free a pgtable from the ropool */
uintptr_t hkip_ropool_pgtable_alloc(void);
void hkip_ropool_pgtable_free(uintptr_t pgd);
/*
 * Signal the module loading status
 */
void hkip_module_status(int load);

/*
 * Run the enable routine at startup(only if the cmdline carry "hkip_enabled")
 */
int hkip_enable(void);

int register_hkip_notifier(struct notifier_block *nb);
int unregister_hkip_notifier(struct notifier_block *nb);
int hkip_insn_write(uintptr_t addr, uint32_t insn);

static inline bool hkip_enabled(void)
{
	return hkip_status;
}

static inline bool is_hkip_normal_level(void)
{
	return hkip_safety_level == HKIP_NORMAL_LEVEL;
}

static inline bool hkip_va_ro_pool(uintptr_t va)
{
	if (va > (uintptr_t)__va(ro_mem_block_start)
		&& va < (uintptr_t)__va(ro_mem_block_end))
		return true;

	return false;
}

static inline unsigned long invoke_smc_fn(unsigned long function_id,
					  unsigned long arg0,
					  unsigned long arg1,
					  unsigned long arg2)
{
	struct arm_smccc_res res;

	arm_smccc_smc(function_id, arg0, arg1, arg2, 0, 0, 0, 0, &res);
	/* precedence order of the judgment is determined according to
	 the frequency of occurrence */
	if (function_id != HKIP_INSN_WRITE && !(arg1 & (PTE_PXN | PTE_RDONLY))) {
		if (res.a0 == 0 && (arg1 & PTE_VALID)
			&& atomic64_read(&hkip_dumpstack) == 0
			&& function_id != HKIP_SCAN_PGD) {
			if (!((arg1 & PMD_TABLE_BIT) != 0 && function_id != HKIP_PTE_WRITE))
				dump_stack();
		}
	}

	return res.a0;
}

phys_addr_t __init hkip_early_pgtable_alloc(void);
void __init hkip_reserve_crashkernel_limit(void);
int __pte_alloc_kernel_hkip(pmd_t *pmd, unsigned long address);
#else /* CONFIG_EULEROS_HKIP_SERVER */
static inline void hkip_module_status(int load)
{
}
static inline int hkip_enable(void)
{
	return 0;
}
static inline void __init hkip_reserve_crashkernel_limit(void)
{
}
#endif /* CONFIG_EULEROS_HKIP_SERVER */
#endif /* __ASSEMBLY__ */
#endif /* _HKIP_H */
